// Shell.

#include "types.h"
#include "user.h"
#include "fcntl.h"


#define MAX_ENV_VAR_NAME 128
#define MAX_ENV_VAR_VAL 1024

typedef struct variable
{
  char name[MAX_ENV_VAR_NAME];
  char val[MAX_ENV_VAR_VAL];
  struct variable *next;
} variable;

/* Adds a new variable with given name and val to a chain (linked list) of variables,
pointed by head. Returns the new head */
variable* addToEnvironment( char* name,  char* val, variable* head);

/* Removes variables indicated by name from the variable chain.
Returns the new head*/
variable* removeFromEnvironment( char* name, variable* head);

/* Releases all memory that was allocated for the chain */
void freeEnvironment(variable* head);

/* Finds a variable by its name. Returns NULL if not found */
variable* findByName( char* name, variable* head);

/* nir */
void executeFromPath();

void initEnv();
void envPrint();
void updateEnv(char * line, char delim);

// Parsed command representation
#define EXEC  1
#define REDIR 2
#define PIPE  3
#define LIST  4
#define BACK  5

#define MAXARGS 10
#define MAXBUFLEN 512

//Environment variables list
variable * envHead;

struct cmd {
  int type;
};

struct execcmd {
  int type;
  char *argv[MAXARGS];
  char *eargv[MAXARGS];
};

struct redircmd {
  int type;
  struct cmd *cmd;
  char *file;
  char *efile;
  int mode;
  int fd;
};

struct pipecmd {
  int type;
  struct cmd *left;
  struct cmd *right;
};

struct listcmd {
  int type;
  struct cmd *left;
  struct cmd *right;
};

struct backcmd {
  int type;
  struct cmd *cmd;
};

int fork1(void);  // Fork but panics on failure.
void panic(char*);
struct cmd *parsecmd(char*);

// Execute cmd.  Never returns.
void
runcmd(struct cmd *cmd)
{
  int p[2];
  struct backcmd *bcmd;
  struct execcmd *ecmd;
  struct listcmd *lcmd;
  struct pipecmd *pcmd;
  struct redircmd *rcmd;

  if(cmd == 0)
    exit();

  switch(cmd->type){
  default:
    panic("runcmd");

  case EXEC:
    ecmd = (struct execcmd*)cmd;
    if(ecmd->argv[0] == 0)
      exit();
    exec(ecmd->argv[0], ecmd->argv);
    /* nir - if execution was seccusfull the next code won't be executed.
       in case the execution failed, try finding the executable file in one of the directories which is listed in the path variable.
       and try to execut it again. */
    executeFromPath(ecmd->argv[0], ecmd->argv);

    printf(2, "exec %s failed\n", ecmd->argv[0]);
    break;

  case REDIR:
    rcmd = (struct redircmd*)cmd;
    close(rcmd->fd);
    if(open(rcmd->file, rcmd->mode) < 0){
      printf(2, "open %s failed\n", rcmd->file);
      exit();
    }
    runcmd(rcmd->cmd);
    break;

  case LIST:
    lcmd = (struct listcmd*)cmd;
    if(fork1() == 0)
      runcmd(lcmd->left);
    wait();
    runcmd(lcmd->right);
    break;

  case PIPE:
    pcmd = (struct pipecmd*)cmd;
    if(pipe(p) < 0)
      panic("pipe");
    if(fork1() == 0){
      close(1);
      dup(p[1]);
      close(p[0]);
      close(p[1]);
      runcmd(pcmd->left);
    }
    if(fork1() == 0){
      close(0);
      dup(p[0]);
      close(p[0]);
      close(p[1]);
      runcmd(pcmd->right);
    }
    close(p[0]);
    close(p[1]);
    wait();
    wait();
    break;

  case BACK:
    bcmd = (struct backcmd*)cmd;
    if(fork1() == 0)
      runcmd(bcmd->cmd);
    break;
  }
  exit();
}

int
getcmd(char *buf, int nbuf)
{
  printf(2, "$ ");
  memset(buf, 0, nbuf);
  gets(buf, nbuf);
  if(buf[0] == 0) // EOF
    return -1;
  return 0;
}

int
main(void)
{
  static char buf[100];
  static char tmpbuf[100];
  int fd;
  int spaceCount;
  int i;
  char delimSpace = ' ';

  int  _wtime;
  int  _rtime;
  int deadpid;

  // Assumes three file descriptors open.
  while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
  }


  // Initialize environment variables from .env, is present
  envHead = 0;
  initEnv();
  //envPrint();

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
	  //First of insert into our history.
	  // Note: the history is managed by the same functions as the environment, since it's
	  // a pretty generic linked list implementation

    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      // Clumsy but will have to do for now.
      // Chdir has no effect on the parent if run in the child.
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        printf(2, "cannot cd %s\n", buf+3);
      continue;
    }
    else if (buf[0] == 's' && buf[1] == 'e' && buf[2] == 't'){   // set command
    	if(buf[3] == '\n') // just display the whole environment
    		envPrint();
    	else{
    		spaceCount=0;
    		for(i=3;buf[i];i++)
    			if (buf[i] == ' ')
    				spaceCount++;

    		if (spaceCount==2){ // we got a proper "set <VAR> <VALUE>" command
    			memset(tmpbuf, 0, 100);
    			strncpy(tmpbuf,buf,100);
    			updateEnv(tmpbuf+4, delimSpace);
    		}
    		else
    			printf(2, "invalid set syntax\n");
    	}
    	continue;

    }
    if(fork1() == 0)
      runcmd(parsecmd(buf));
    deadpid = wait2(&_wtime, &_rtime);
    //printf(1, "Finished pid=%d, wtime=%d, rtime=%d\n", deadpid, _wtime, _rtime);
  }
  freeEnvironment(envHead);
  exit();
}

void
panic(char *s)
{
  printf(2, "%s\n", s);
  exit();
}

int
fork1(void)
{
  int pid;

  pid = fork();
  if(pid == -1)
    panic("fork");
  return pid;
}

// Constructors

struct cmd*
execcmd(void)
{
  struct execcmd *cmd;

  cmd = malloc(sizeof(*cmd));
  memset(cmd, 0, sizeof(*cmd));
  cmd->type = EXEC;
  return (struct cmd*)cmd;
}

struct cmd*
redircmd(struct cmd *subcmd, char *file, char *efile, int mode, int fd)
{
  struct redircmd *cmd;

  cmd = malloc(sizeof(*cmd));
  memset(cmd, 0, sizeof(*cmd));
  cmd->type = REDIR;
  cmd->cmd = subcmd;
  cmd->file = file;
  cmd->efile = efile;
  cmd->mode = mode;
  cmd->fd = fd;
  return (struct cmd*)cmd;
}

struct cmd*
pipecmd(struct cmd *left, struct cmd *right)
{
  struct pipecmd *cmd;

  cmd = malloc(sizeof(*cmd));
  memset(cmd, 0, sizeof(*cmd));
  cmd->type = PIPE;
  cmd->left = left;
  cmd->right = right;
  return (struct cmd*)cmd;
}

struct cmd*
listcmd(struct cmd *left, struct cmd *right)
{
  struct listcmd *cmd;

  cmd = malloc(sizeof(*cmd));
  memset(cmd, 0, sizeof(*cmd));
  cmd->type = LIST;
  cmd->left = left;
  cmd->right = right;
  return (struct cmd*)cmd;
}

struct cmd*
backcmd(struct cmd *subcmd)
{
  struct backcmd *cmd;

  cmd = malloc(sizeof(*cmd));
  memset(cmd, 0, sizeof(*cmd));
  cmd->type = BACK;
  cmd->cmd = subcmd;
  return (struct cmd*)cmd;
}
// Parsing

char whitespace[] = " \t\r\n\v";
char symbols[] = "<|>&;()";

int
gettoken(char **ps, char *es, char **q, char **eq)
{
  char *s;
  int ret;

  s = *ps;
  while(s < es && strchr(whitespace, *s))
    s++;
  if(q)
    *q = s;
  ret = *s;
  switch(*s){
  case 0:
    break;
  case '|':
  case '(':
  case ')':
  case ';':
  case '&':
  case '<':
    s++;
    break;
  case '>':
    s++;
    if(*s == '>'){
      ret = '+';
      s++;
    }
    break;
  default:
    ret = 'a';
    while(s < es && !strchr(whitespace, *s) && !strchr(symbols, *s))
      s++;
    break;
  }
  if(eq)
    *eq = s;

  while(s < es && strchr(whitespace, *s))
    s++;
  *ps = s;
  return ret;
}

int
peek(char **ps, char *es, char *toks)
{
  char *s;

  s = *ps;
  while(s < es && strchr(whitespace, *s))
    s++;
  *ps = s;
  return *s && strchr(toks, *s);
}

struct cmd *parseline(char**, char*);
struct cmd *parsepipe(char**, char*);
struct cmd *parseexec(char**, char*);
struct cmd *nulterminate(struct cmd*);

struct cmd*
parsecmd(char *s)
{
  char *es;
  struct cmd *cmd;

  es = s + strlen(s);
  cmd = parseline(&s, es);
  peek(&s, es, "");
  if(s != es){
    printf(2, "leftovers: %s\n", s);
    panic("syntax");
  }
  nulterminate(cmd);
  return cmd;
}

struct cmd*
parseline(char **ps, char *es)
{
  struct cmd *cmd;

  cmd = parsepipe(ps, es);
  while(peek(ps, es, "&")){
    gettoken(ps, es, 0, 0);
    cmd = backcmd(cmd);
  }
  if(peek(ps, es, ";")){
    gettoken(ps, es, 0, 0);
    cmd = listcmd(cmd, parseline(ps, es));
  }
  return cmd;
}

struct cmd*
parsepipe(char **ps, char *es)
{
  struct cmd *cmd;

  cmd = parseexec(ps, es);
  if(peek(ps, es, "|")){
    gettoken(ps, es, 0, 0);
    cmd = pipecmd(cmd, parsepipe(ps, es));
  }
  return cmd;
}

struct cmd*
parseredirs(struct cmd *cmd, char **ps, char *es)
{
  int tok;
  char *q, *eq;

  while(peek(ps, es, "<>")){
    tok = gettoken(ps, es, 0, 0);
    if(gettoken(ps, es, &q, &eq) != 'a')
      panic("missing file for redirection");
    switch(tok){
    case '<':
      cmd = redircmd(cmd, q, eq, O_RDONLY, 0);
      break;
    case '>':
      cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE, 1);
      break;
    case '+':  // >>
      cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE, 1);
      break;
    }
  }
  return cmd;
}

struct cmd*
parseblock(char **ps, char *es)
{
  struct cmd *cmd;

  if(!peek(ps, es, "("))
    panic("parseblock");
  gettoken(ps, es, 0, 0);
  cmd = parseline(ps, es);
  if(!peek(ps, es, ")"))
    panic("syntax - missing )");
  gettoken(ps, es, 0, 0);
  cmd = parseredirs(cmd, ps, es);
  return cmd;
}

struct cmd*
parseexec(char **ps, char *es)
{
  char *q, *eq;
  int tok, argc;
  struct execcmd *cmd;
  struct cmd *ret;

  if(peek(ps, es, "("))
    return parseblock(ps, es);

  ret = execcmd();
  cmd = (struct execcmd*)ret;

  argc = 0;
  ret = parseredirs(ret, ps, es);
  while(!peek(ps, es, "|)&;")){
    if((tok=gettoken(ps, es, &q, &eq)) == 0)
      break;
    if(tok != 'a')
      panic("syntax");
    cmd->argv[argc] = q;
    cmd->eargv[argc] = eq;
    argc++;
    if(argc >= MAXARGS)
      panic("too many args");
    ret = parseredirs(ret, ps, es);
  }
  cmd->argv[argc] = 0;
  cmd->eargv[argc] = 0;
  return ret;
}

// NUL-terminate all the counted strings.
struct cmd*
nulterminate(struct cmd *cmd)
{
  int i;
  struct backcmd *bcmd;
  struct execcmd *ecmd;
  struct listcmd *lcmd;
  struct pipecmd *pcmd;
  struct redircmd *rcmd;

  if(cmd == 0)
    return 0;

  switch(cmd->type){
  case EXEC:
    ecmd = (struct execcmd*)cmd;
    for(i=0; ecmd->argv[i]; i++)
      *ecmd->eargv[i] = 0;
    break;

  case REDIR:
    rcmd = (struct redircmd*)cmd;
    nulterminate(rcmd->cmd);
    *rcmd->efile = 0;
    break;

  case PIPE:
    pcmd = (struct pipecmd*)cmd;
    nulterminate(pcmd->left);
    nulterminate(pcmd->right);
    break;

  case LIST:
    lcmd = (struct listcmd*)cmd;
    nulterminate(lcmd->left);
    nulterminate(lcmd->right);
    break;

  case BACK:
    bcmd = (struct backcmd*)cmd;
    nulterminate(bcmd->cmd);
    break;
  }
  return cmd;
}

/*********** Arseny& Nir Code starts here ***********/



/**
 * Gets a string of format "<varName><delimiter><varValue>"
 */
void updateEnv(char * line, char delim){
	int strLen;
	variable * found;
	char * name;
	char * value;

	name = strtok(line, &delim);
	value = strtok(0, &delim);

	// Chomp \n from value
	strLen = strlen(value);
	value[strLen-1] = '\0';

	found = findByName(name, envHead);
	if ((int)found){          // variable exists, setting new value
	     envHead = removeFromEnvironment(name, envHead);
	     envHead = addToEnvironment(name,value,envHead);
	}
	else				// new variable
		envHead = addToEnvironment(name,value,envHead);

}

void initEnv(){
	char iniValFile[5] = ".env";
	char * begin;
	char delimEq = '=';
	int fd;
	char buf1[MAXBUFLEN];

	if ((fd = open(iniValFile, O_RDONLY)) <0)
		return;


	for(begin = getsfd(buf1, MAXBUFLEN, fd); (*begin); begin = getsfd(buf1, MAXBUFLEN, fd)){
		updateEnv(begin,delimEq);
	}

}


int comp(char* s1, char* s2)
{
  return strcmp(s1,s2) == 0;
}

variable* findByName( char* name, variable* head)
{
  if (!name)
    return 0;

  while (head)
  {
    if (comp(name, head->name))
      break;
    head = head->next;
  }
  return head;
}

variable* removeFromEnvironment(char* name, variable* head)
{
  variable* tmp;
  if (!head)
    return 0;
  if (!name || (*name == 0) )
    return head;

  if (comp(head->name,name))
  {
    tmp = head->next;
    free(head);
    return tmp;
  }

  head->next = removeFromEnvironment(name, head->next);
  return head;
}

variable* addToEnvironment(char* name, char* val, variable* head)
{
  if (!name || !val)
    return head;

  if (head == 0)
  {
    variable* newVar = (variable*) malloc( sizeof(variable) );
    strcpy(newVar->name, name);
    strcpy(newVar->val, val);
    newVar->next = 0;
    head = newVar;
  }
  else
    head->next = addToEnvironment(name, val, head->next);

  return head;
}

void freeEnvironment(variable* head)
{
  if (!head)
    return;
  freeEnvironment(head->next);
  free(head);
}

void envPrint(){
	variable * tmpHead = envHead;

	while (tmpHead){
		printf(1, "%s = ", tmpHead->name);

		tmpHead = tmpHead->next;
	}
}

/* this method parsing all the paths from the .env file.
 it searches the executable file in each path and try to execute it.
*/
void executeFromPath(char* fileName, char** argv){
	char* delim = ":";	// the delimiter of the paths in the .env file.
	char* toExecute = malloc(250 * sizeof (char));	// a variable which will contain the path to execute.
	char* fileNameTmp = malloc(250 * sizeof (char));
	if ((fileNameTmp == 0)||(toExecute == 0)) {
	    /* Memory could not be allocated, the program should
	       handle the error here as appropriate. */
	} else {

		variable* path = findByName("PATH",envHead); // geting the path sting from the env.
		fileNameTmp = strcpy(fileNameTmp,fileName);
		char * value = strtok(path->val, delim); // the first path.
		toExecute = strcat(strcpy(toExecute,value),fileNameTmp);	//concat the fileName to the path.

		// going over all the paths in the .env file.
		while (value != 0){
			exec(toExecute, argv);
			value = strtok(0, delim); // the next path.
			toExecute = strcat(strcpy(toExecute,value),fileNameTmp); // the string to execute.
		}
		free(fileNameTmp);
		free(toExecute);
		fileNameTmp = 0;
		toExecute = 0;
	}
}

void executeFromPath_old(char* fileName, char** argv){
	char* delim = ":";	// the delimiter of the paths in the .env file.
	char* toExecute = "";	// a variable which will contain the path to execute.
	char* fileNameTmp = 0;
	variable* path = findByName("PATH",envHead); // geting the path sting from the env.

	fileNameTmp = strcpy(fileNameTmp,fileName);
	char * value = strtok(path->val, delim); // the first path.
	toExecute = strcat(strcpy(toExecute,value),fileNameTmp);	//concat the fileName to the path.

	// going over all the paths in the .env file.
	while (value != 0){
		exec(toExecute, argv);
		value = strtok(0, delim); // the next path.
		toExecute = strcat(strcpy(toExecute,value),fileNameTmp); // the string to execute.
	}

}
